#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AutoML顺序执行搜索策略示例
Sequential Execution Search Strategy Example

本示例演示如何使用Auto-sklearn的顺序执行模式进行模型训练和集成构建。
顺序执行模式将模型训练和集成构建分为两个独立的阶段，适用于资源受限环境。

作者: AutoML学习团队
日期: 2024-07-12
"""

import os
import time
import warnings
from datetime import datetime
from pprint import pprint

import numpy as np
import sklearn.datasets
import sklearn.metrics
import sklearn.model_selection
from sklearn.metrics import classification_report, confusion_matrix

import autosklearn.classification
from autosklearn.ensembles.ensemble_selection import EnsembleSelection

# 忽略警告信息
warnings.filterwarnings('ignore')


class SequentialExecutionController:
    """
    AutoML顺序执行控制器
    
    控制Auto-sklearn的顺序执行流程，将模型训练和集成构建分为两个阶段：
    1. 模型训练阶段：搜索和训练多个候选模型
    2. 集成构建阶段：基于训练好的模型构建最终集成
    """
    
    def __init__(self, time_budget=60, tmp_folder=None):
        """
        初始化顺序执行控制器
        
        参数:
            time_budget (int): 总时间预算（秒）
            tmp_folder (str): 临时文件夹路径
        """
        self.time_budget = time_budget
        self.tmp_folder = tmp_folder or f"/tmp/autosklearn_sequential_{int(time.time())}"
        self.training_start_time = None
        self.training_end_time = None
        self.ensemble_start_time = None
        self.ensemble_end_time = None
        self.automl = None
        
        print(f"🔧 初始化顺序执行控制器")
        print(f"   时间预算: {time_budget}秒")
        print(f"   临时目录: {self.tmp_folder}")
    
    def create_automl_classifier(self):
        """
        创建Auto-sklearn分类器（顺序执行模式）
        
        关键配置:
        - ensemble_class=None: 禁用并行集成构建
        - delete_tmp_folder_after_terminate=False: 保留中间结果
        """
        print(f"\n🏗️  创建Auto-sklearn分类器（顺序执行模式）")
        
        self.automl = autosklearn.classification.AutoSklearnClassifier(
            time_left_for_this_task=self.time_budget,
            per_run_time_limit=int(self.time_budget / 6),  # 单次运行时间限制
            tmp_folder=self.tmp_folder,
            
            # 顺序执行关键配置
            ensemble_class=None,  # 禁用并行集成构建
            delete_tmp_folder_after_terminate=False,  # 保留中间结果
            
            # 优化配置
            initial_configurations_via_metalearning=25,  # 使用元学习初始配置
            include_estimators=None,  # 包含所有估计器
            exclude_estimators=None,  # 不排除任何估计器
            
            # 内存和并发控制
            memory_limit=3072,  # 内存限制3GB
            n_jobs=1,  # 单线程执行
            
            # 输出控制
            logging_config=None,  # 使用默认日志配置
            seed=42  # 固定随机种子
        )
        
        print(f"   ✅ 分类器创建完成")
        print(f"   📁 临时目录: {self.tmp_folder}")
        print(f"   ⏰ 时间预算: {self.time_budget}秒")
        print(f"   🧮 单次运行限制: {int(self.time_budget / 6)}秒")
    
    def run_training_phase(self, X_train, y_train, dataset_name="breast_cancer"):
        """
        执行模型训练阶段
        
        参数:
            X_train: 训练特征
            y_train: 训练标签
            dataset_name: 数据集名称
        """
        print(f"\n🚀 开始模型训练阶段")
        print(f"   数据集: {dataset_name}")
        print(f"   训练样本: {X_train.shape[0]}个")
        print(f"   特征数量: {X_train.shape[1]}个")
        print(f"   类别分布: {np.bincount(y_train)}")
        
        self.training_start_time = time.time()
        
        try:
            # 执行模型训练（不构建集成）
            self.automl.fit(X_train, y_train, dataset_name=dataset_name)
            self.training_end_time = time.time()
            
            training_duration = self.training_end_time - self.training_start_time
            print(f"\n✅ 模型训练阶段完成")
            print(f"   耗时: {training_duration:.2f}秒")
            print(f"   成功训练的模型数量: {len(self.automl.show_models())}")
            
            # 显示训练统计信息
            print(f"\n📊 训练阶段统计信息:")
            print(self.automl.sprint_statistics())
            
        except Exception as e:
            print(f"❌ 训练阶段失败: {str(e)}")
            raise
    
    def run_ensemble_phase(self, y_train):
        """
        执行集成构建阶段
        
        参数:
            y_train: 训练标签（用于集成验证）
        """
        print(f"\n🔧 开始集成构建阶段")
        
        self.ensemble_start_time = time.time()
        
        try:
            # 使用EnsembleSelection策略构建集成
            self.automl.fit_ensemble(y_train, ensemble_class=EnsembleSelection)
            self.ensemble_end_time = time.time()
            
            ensemble_duration = self.ensemble_end_time - self.ensemble_start_time
            print(f"\n✅ 集成构建阶段完成")
            print(f"   耗时: {ensemble_duration:.2f}秒")
            
            # 显示最终集成模型
            print(f"\n🏆 最终集成模型:")
            pprint(self.automl.show_models(), indent=4)
            
        except Exception as e:
            print(f"❌ 集成构建失败: {str(e)}")
            raise
    
    def get_performance_summary(self):
        """
        获取性能摘要
        
        返回:
            dict: 包含各阶段耗时的性能摘要
        """
        training_time = (self.training_end_time - self.training_start_time 
                        if self.training_end_time else 0)
        ensemble_time = (self.ensemble_end_time - self.ensemble_start_time 
                        if self.ensemble_end_time else 0)
        total_time = training_time + ensemble_time
        
        return {
            'training_time': training_time,
            'ensemble_time': ensemble_time,
            'total_time': total_time,
            'training_percentage': training_time / total_time * 100 if total_time > 0 else 0,
            'ensemble_percentage': ensemble_time / total_time * 100 if total_time > 0 else 0
        }


class PerformanceAnalyzer:
    """
    性能分析器
    
    分析和评估顺序执行模式的性能表现，包括预测准确性和执行效率。
    """
    
    def __init__(self, automl_model):
        """
        初始化性能分析器
        
        参数:
            automl_model: 训练好的AutoML模型
        """
        self.automl = automl_model
    
    def evaluate_predictions(self, X_test, y_test):
        """
        评估模型预测性能
        
        参数:
            X_test: 测试特征
            y_test: 测试标签
            
        返回:
            dict: 评估结果
        """
        print(f"\n📈 模型性能评估")
        
        # 生成预测结果
        predictions = self.automl.predict(X_test)
        probabilities = self.automl.predict_proba(X_test)
        
        # 计算各种评估指标
        accuracy = sklearn.metrics.accuracy_score(y_test, predictions)
        precision = sklearn.metrics.precision_score(y_test, predictions, average='weighted')
        recall = sklearn.metrics.recall_score(y_test, predictions, average='weighted')
        f1 = sklearn.metrics.f1_score(y_test, predictions, average='weighted')
        auc = sklearn.metrics.roc_auc_score(y_test, probabilities[:, 1])
        
        # 混淆矩阵
        cm = confusion_matrix(y_test, predictions)
        
        print(f"   准确率 (Accuracy): {accuracy:.4f}")
        print(f"   精确率 (Precision): {precision:.4f}")
        print(f"   召回率 (Recall): {recall:.4f}")
        print(f"   F1分数: {f1:.4f}")
        print(f"   AUC: {auc:.4f}")
        
        print(f"\n📊 分类报告:")
        print(classification_report(y_test, predictions, 
                                  target_names=['良性 (Benign)', '恶性 (Malignant)']))
        
        print(f"\n🔍 混淆矩阵:")
        print("实际\\预测   良性   恶性")
        print(f"良性      {cm[0,0]:4d}   {cm[0,1]:4d}")
        print(f"恶性      {cm[1,0]:4d}   {cm[1,1]:4d}")
        
        return {
            'accuracy': accuracy,
            'precision': precision,
            'recall': recall,
            'f1_score': f1,
            'auc': auc,
            'confusion_matrix': cm,
            'predictions': predictions,
            'probabilities': probabilities
        }
    
    def analyze_model_diversity(self):
        """
        分析模型多样性
        """
        print(f"\n🔬 模型多样性分析")
        
        models = self.automl.show_models()
        if not models:
            print("   ⚠️  没有可用的模型进行分析")
            return
        
        # 统计不同算法类型
        algorithm_counts = {}
        for weight, model in models.items():
            algorithm = type(model).__name__
            algorithm_counts[algorithm] = algorithm_counts.get(algorithm, 0) + 1
        
        print(f"   模型总数: {len(models)}")
        print(f"   算法类型数: {len(algorithm_counts)}")
        print(f"   算法分布:")
        for algorithm, count in sorted(algorithm_counts.items()):
            print(f"     {algorithm}: {count}个")
    
    def save_results(self, performance_summary, evaluation_results, output_dir="../results"):
        """
        保存分析结果
        
        参数:
            performance_summary: 性能摘要
            evaluation_results: 评估结果
            output_dir: 输出目录
        """
        # 创建结果目录
        os.makedirs(output_dir, exist_ok=True)
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        # 保存性能摘要
        summary_file = os.path.join(output_dir, f"performance_summary_{timestamp}.txt")
        with open(summary_file, 'w', encoding='utf-8') as f:
            f.write("AutoML顺序执行性能摘要\n")
            f.write("=" * 50 + "\n\n")
            f.write(f"训练时间: {performance_summary['training_time']:.2f}秒 "
                   f"({performance_summary['training_percentage']:.1f}%)\n")
            f.write(f"集成时间: {performance_summary['ensemble_time']:.2f}秒 "
                   f"({performance_summary['ensemble_percentage']:.1f}%)\n")
            f.write(f"总计时间: {performance_summary['total_time']:.2f}秒\n\n")
            
            f.write("预测性能:\n")
            f.write(f"准确率: {evaluation_results['accuracy']:.4f}\n")
            f.write(f"精确率: {evaluation_results['precision']:.4f}\n")
            f.write(f"召回率: {evaluation_results['recall']:.4f}\n")
            f.write(f"F1分数: {evaluation_results['f1_score']:.4f}\n")
            f.write(f"AUC: {evaluation_results['auc']:.4f}\n")
        
        print(f"\n💾 结果已保存到: {summary_file}")


def main():
    """
    主函数：演示AutoML顺序执行搜索策略
    """
    print("🎯 AutoML顺序执行搜索策略示例")
    print("=" * 60)
    
    # 1. 数据加载和预处理
    print("\n📊 加载Wisconsin乳腺癌数据集")
    X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)
    X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(
        X, y, test_size=0.3, random_state=42, stratify=y
    )
    
    print(f"   数据集形状: {X.shape}")
    print(f"   训练集: {X_train.shape[0]}样本")
    print(f"   测试集: {X_test.shape[0]}样本")
    print(f"   特征数量: {X_train.shape[1]}个")
    print(f"   类别分布 - 训练集: 良性{np.sum(y_train==0)}, 恶性{np.sum(y_train==1)}")
    print(f"   类别分布 - 测试集: 良性{np.sum(y_test==0)}, 恶性{np.sum(y_test==1)}")
    
    # 2. 创建和配置顺序执行控制器
    controller = SequentialExecutionController(
        time_budget=60,  # 1分钟时间预算
        tmp_folder="/tmp/autosklearn_sequential_breast_cancer"
    )
    
    # 3. 创建AutoML分类器
    controller.create_automl_classifier()
    
    # 4. 执行模型训练阶段
    controller.run_training_phase(X_train, y_train, "breast_cancer")
    
    # 5. 执行集成构建阶段
    controller.run_ensemble_phase(y_train)
    
    # 6. 性能分析
    analyzer = PerformanceAnalyzer(controller.automl)
    
    # 获取性能摘要
    performance_summary = controller.get_performance_summary()
    
    print(f"\n⏱️  执行时间分析:")
    print(f"   训练阶段: {performance_summary['training_time']:.2f}秒 "
          f"({performance_summary['training_percentage']:.1f}%)")
    print(f"   集成阶段: {performance_summary['ensemble_time']:.2f}秒 "
          f"({performance_summary['ensemble_percentage']:.1f}%)")
    print(f"   总计时间: {performance_summary['total_time']:.2f}秒")
    
    # 评估预测性能
    evaluation_results = analyzer.evaluate_predictions(X_test, y_test)
    
    # 分析模型多样性
    analyzer.analyze_model_diversity()
    
    # 保存结果
    analyzer.save_results(performance_summary, evaluation_results)
    
    # 7. 生成最终报告
    print(f"\n🎉 顺序执行策略演示完成!")
    print(f"   最终准确率: {evaluation_results['accuracy']:.4f}")
    print(f"   总执行时间: {performance_summary['total_time']:.2f}秒")
    print(f"   模型数量: {len(controller.automl.show_models())}")
    
    # 8. 清理建议
    print(f"\n🧹 清理建议:")
    print(f"   临时文件夹: {controller.tmp_folder}")
    print(f"   如需清理，请手动删除临时文件夹")
    
    return controller, analyzer, performance_summary, evaluation_results


if __name__ == "__main__":
    # 设置随机种子以确保结果可重现
    np.random.seed(42)
    
    try:
        controller, analyzer, performance_summary, evaluation_results = main()
        print(f"\n✅ 程序执行成功!")
        
    except KeyboardInterrupt:
        print(f"\n⚠️  程序被用户中断")
        
    except Exception as e:
        print(f"\n❌ 程序执行失败: {str(e)}")
        import traceback
        traceback.print_exc()
        
    finally:
        print(f"\n👋 感谢使用AutoML顺序执行示例!")